package james.gan.config.spring;

import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

import james.gan.common.ExtensionFactory;
import james.gan.common.ExtensionLoader;
import james.gan.common.Request;
import james.gan.common.Response;
import james.gan.config.api.YetConfig;
import james.gan.config.spring.annotation.RpcConfig;
import james.gan.config.spring.annotation.RpcCustom;
import james.gan.config.spring.annotation.RpcService;
import james.gan.config.spring.bean.ServiceDefinition;
import james.gan.container.ServiceContainer;
import james.gan.registry.api.Pair;
import james.gan.registry.api.Registry;
import james.gan.registry.api.RegistryCenter;
import james.gan.registry.api.RegistryCenterFacroty;
import james.gan.registry.api.RegistryCustomCache;
import james.gan.remoting.api.Client;
import james.gan.remoting.api.Remoting;
import james.gan.remoting.api.RemotingFacroty;
import james.gan.remoting.api.RemotingFutureDispatchMonitor.RemotingFuture;
import james.gan.remoting.api.Server;
import james.gan.rpc.protocol.api.Protocol;
import james.gan.rpc.protocol.api.ProtocolFacroty;
import james.gan.serialization.api.Serialization;
import james.gan.serialization.api.SerializationFacroty;

/**
 * @author suc
 */
@SuppressWarnings("serial")
@Service
@Order(1)
public class SpringConfig extends YetConfig
		implements ApplicationContextAware, BeanFactoryPostProcessor, BeanPostProcessor,ApplicationListener<ContextRefreshedEvent>{

	private ConfigurableListableBeanFactory beListableBeanFactory;

	private static final Logger logger = LoggerFactory.getLogger(SpringConfig.class);
	
	@SuppressWarnings("rawtypes")
	protected static final Map<Class<?>, ExtensionFactory> EXTENSION_FACTORYS = new ConcurrentHashMap<>();

	private ApplicationContext applicationContext;

	private RegistryCenter registryCenter;

	private RegistryCustomCache cache;

	private Serialization serialization;

	private Protocol protocol;

	private Remoting remoting;

	private ServiceContainer springServiceContainer;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
		this.springServiceContainer = new SpringServiceContainer(applicationContext);
		initExtensionFactory();
		parseRpcConfig(applicationContext.getBeanNamesForAnnotation(RpcConfig.class));
		parseService(applicationContext.getBeanNamesForAnnotation(RpcService.class));
		this.cache = new RegistryCustomCache(registryCenter);
		registryCenter.addObserver(cache);
	}

	private void parseService(String[] services) {
		if (services == null || services.length == 0) {
			return;
		}
		for (String name : services) {
			Object bean = applicationContext.getBean(name);
			RpcService rpcService = AnnotationUtils.findAnnotation(bean.getClass(), RpcService.class);
			Class<?> interfaceClass = rpcService.interfaceClass();
			String value = rpcService.value();
			boolean push = rpcService.push();
			int port = rpcService.port();
			Registry registry = new Registry();
			registry.setName(value);
			registry.setStatus(push ? Registry.Status.PUSH : Registry.Status.DOWN);
			registryCenter.registe(new InetSocketAddress(port), interfaceClass, registry);
			openServer(value, port);
		}
	}

	private void openServer(String name, int port) {
		Server server = remoting.getServer();
		try {
			server.start(port);
		} catch (Exception e) {
			logger.error("Server:[" + name + "] Started failed", e);
		}
	}

	public List<ServiceDefinition> parseServiceDefinition(ApplicationContext applicationContext, String[] services) {
		List<ServiceDefinition> list = new ArrayList<>();
		for (String service : services) {
			Object bean = applicationContext.getBean(service);
			RpcService rpcService = AnnotationUtils.findAnnotation(bean.getClass(), RpcService.class);
			Class<?> interfaceClass = rpcService.interfaceClass();
			String name = rpcService.value();
			boolean push = rpcService.push();
			list.add(new ServiceDefinition(interfaceClass, name, push));
		}
		return list;
	}

	private void parseRpcConfig(String[] configs) {
		String config = null;
		if (configs == null || configs.length == 0) {
			throw new IllegalArgumentException("Need one @RpcConfig Configration class");
		}
		if (configs.length >= 1) {
			config = configs[0];
			logger.info("RpcConfig found : {} , choose :{}", configs.length, config);
		}
		Object bean = applicationContext.getBean(config);
		RpcConfig rpcConfig = AnnotationUtils.findAnnotation(bean.getClass(), RpcConfig.class);
		Class<? extends Remoting> remotingType = rpcConfig.remotingType();// 通信协议
		Class<? extends Serialization> serializationType = rpcConfig.serializationType();
		Class<? extends Protocol> protocolType = rpcConfig.Protocol();
		String registry = rpcConfig.registry();// 注册中心字符串
		Pair<String, String> registryPair = parseRegistryStr(registry);
		protocol = ProtocolFacroty.get(protocolType);
		registryCenter = RegistryCenterFacroty.get(registryPair.getLeft());
		serialization = getSerialization(serializationType);
		remoting = getRemoting(remotingType);
	}

	private Serialization getSerialization(Class<?> clzs) {
		if (clzs.equals(Serialization.class)) {
			clzs = protocol.getSerializationType();
		}
		return SerializationFacroty.get(clzs);
	}

	private Remoting getRemoting(Class<?> clzs) {
		if (clzs.equals(Remoting.class)) {
			clzs = protocol.getRemotingType();
		}
		Remoting remoting = RemotingFacroty.get(clzs);
		remoting.setSerialization(serialization);
		remoting.setServiceContainer(springServiceContainer);
		return remoting;
	}

	private Pair<String, String> parseRegistryStr(String registry) {
		int tag = registry.indexOf(":");
		String type = registry.substring(0, tag);
		String address = registry.substring(tag + 1, registry.length());
		return new Pair<String, String>(type, address);
	}

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		this.beListableBeanFactory = beanFactory;
	}

	public void proxyCustom(Field[] fields, Object bean) {
		for (Field f : fields) {
			RpcCustom[] customs = f.getAnnotationsByType(RpcCustom.class);
			RpcCustom custom = customs[0];
			Class<?> type = f.getType();
			// String value = custom.value();
			registerSingleton(f.getName(),createProxyCustom(type));
		}
		autowire(bean);
	}

	public void registerSingleton(String name, Object obj){
		if(!applicationContext.containsBean(name)){
			beListableBeanFactory.registerSingleton(name, obj);
		}
	}
	
	private void autowire(Object target) {
		beListableBeanFactory.autowireBean(target);
	}

	private Object createProxyCustom(Class<?> type) {
		return Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] { type }, new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				Request request = new Request();
				request.setId(UUID.randomUUID().toString());
				request.setMethodName(method.getName());
				request.setParameterTypes(method.getParameterTypes());
				request.setClassName(method.getDeclaringClass());
				request.setParameters(args);
				SocketAddress socketAddress = cache.getLoadBlanceSocketAddress(type);
				Client client = remoting.getClient();
				RemotingFuture future = client.send(socketAddress, request);
				Response response = future.get();
				if (response.getThrowable() != null) {
					throw new RuntimeException(response.getThrowable());
				}
				return response.getResult();
			}
		});
	}
	
	
	private void initExtensionFactory(){
		ExtensionLoader.load(ExtensionFactory.class, this.getClass().getClassLoader()).stream().forEach(e -> {
			EXTENSION_FACTORYS.put(ExtensionFactory.class, e);
		});
	}

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		Field[] fields = bean.getClass().getDeclaredFields();
		WeakReference<List<Field>> tmp = new WeakReference<>(new ArrayList<>());
		for (Field f : fields) {
			RpcCustom[] customs = f.getAnnotationsByType(RpcCustom.class);
			if (customs.length == 0) {
				break;
			}
			tmp.get().add(f);
		}
		if(tmp.get().size()>0){
			proxyCustom(tmp.get().toArray(new Field[]{}),bean);
		}
		return bean;
	}

	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		//parseCustom(customsNames.toArray(new String[]{}));
	}

}
